using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Security;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using System.Xml.XPath;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebPartPages.Communication;
using Mvp.Xml.Exslt;
using PS.WebParts.Rollup;
using PS.WebParts.Rollup.Core;
using Strings = PS.WebParts.Rollup.Strings;
using WebPart = Microsoft.SharePoint.WebPartPages.WebPart;


//Step #1: Reference the Communication namespace.

namespace PS.WebParts
{
    [XmlRoot(Namespace = "PS.WebParts.Rollup"),
     ToolboxData("<{0}:RollUpDataFormWebPart runat=server></{0}:RollUpDataFormWebPart>")]
    public class RollUpDataFormWebPart : DataFormWebPart, IListProvider
    {
        internal string DataSourcePrefix =
            string.Format("<%@ Register TagPrefix=\"webparts\" Namespace=\"PS.WebParts\" Assembly=\"{0}\" %>", typeof(RollUpDataFormWebPart).Assembly.FullName);

        private ATabularDataSource _dsc;
        private DateTime timeQueryEnd;
        private DateTime timeQueryStart;
        private DateTime timeRenderEnd;
        private DateTime timeRenderStart;

        [Category("Data Rollup"), FriendlyName("Fix Sorting"), Description("Fixes sort types in number and date/time columns"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared)]
        public bool FixSorting { get; set; }

        protected internal ATabularDataSource TabularDataSource
        {
            get
            {
                if (_dsc == null)
                {
                    foreach (object o in base.DataSources)
                    {
                        _dsc = o as ATabularDataSource;
                        if (_dsc != null)
                            break;
                    }
                }
                return _dsc;
            }
        }

        /// <summary>
        /// Returns raw xml data. The different sources are consolidated into one result set (DocumentElements/Rows*)
        /// </summary>                
        /// <remarks>Used by RollupDS, to provide data as an xml datasource. Author: MJM</remarks>
        /// <returns><DocumentElement><Rows><_ListId>Guid</_ListId><_WebId>Guid</_WebId><_ItemId>1</_ItemId><Title>Title</Title></Rows></returns>
        public string GetXmlData()
        {
            string xml = string.Empty;

            if (TabularDataSource != null)
            {
                ATabularDataSource ds = TabularDataSource;

                #region DataSource
                Object type = ds.GetView().GetData();

                bool isDataTable = type is DataTable;

                if (isDataTable || type is DataSet)
                {
                    if (isDataTable)
                    {
                        var sw = new StringWriter();

                        var dt = (DataTable)type;

                        if (string.IsNullOrEmpty(dt.TableName))
                            dt.TableName = "Rows";

                        dt.WriteXml(sw);

                        xml = sw.GetStringBuilder().ToString();
                    }
                    else
                    {
                        xml = ((DataSet)type).GetXml();
                    }

                    return xml;
                }
                #endregion
            }

            #region DataSources

            DataSourceCollection sources = this.DataSources;// ds.GetView().CachedDataSources;

            if (sources != null)
            {
                DataTable result = null;

                xml = string.Empty;

                foreach (Control dsrc in sources)
                {
                    ATabularDataSourceView view = ((ATabularDataSource)dsrc).GetView();

                    view.ForceRollupRerun();

                    DataTable dt = view.GetData();

                    if (dt == null) continue;

                    if (string.IsNullOrEmpty(dt.TableName))
                        dt.TableName = "Rows";

                    if (result != null)
                        result.Merge(dt);
                    else
                        result = dt.Copy();
                }

                if (result != null)
                {
                    var sw = new StringWriter();

                    result.WriteXml(sw);

                    xml = sw.GetStringBuilder().ToString();

                    return xml;
                }
            }
            #endregion

            // Should not get to this point
            //TODO: Improve case
            return xml;
        }

        [Category("Data Rollup"), FriendlyName("Debug"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared)]
        public bool Debug { get; set; }

        internal Dictionary<string, string> GetFilter()
        {
            var ret = new Dictionary<string, string>();

            Hashtable ht = ParameterValues.Collection;

            if (ht.ContainsKey("dvt_sortfield") && ht["dvt_sortfield"] != null)
                ret.Add("sortfield", ht["dvt_sortfield"].ToString());


            if (ht.ContainsKey("dvt_sorttype") && ht["dvt_sorttype"] != null)
                ret.Add("sorttype", ht["dvt_sorttype"].ToString());


            if (ht.ContainsKey("dvt_sortdir") && ht["dvt_sortdir"] != null)
                ret.Add("sortdir", ht["dvt_sortdir"].ToString());

            if (ht.ContainsKey("dvt_filterval") && ht["dvt_filterval"] != null)
                ret.Add("filterval", ht["dvt_filterval"].ToString());

            if (ht.ContainsKey("dvt_filterfield") && ht["dvt_filterfield"] != null)
                ret.Add("filterfield", ht["dvt_filterfield"].ToString());

            return ret;
        }

        public override ToolPart[] GetToolParts()
        {
            var toolParts = new List<ToolPart>(base.GetToolParts());
            //RollUpToolpart toolpart = new RollUpToolpart();
            //toolpart.partName = "RollUp Data View";
            var toolpart = new RollUpToolpartProp(this);
            toolpart.Title = "Data Rollup Properties";
            toolParts.Insert(0, toolpart);
            return toolParts.ToArray();
        }

        protected override void CreateChildControls()
        {
            if (!AllPropertiesSet())
            {
                DataSourceID = "";
                DataSource = null;
                if (Xsl == null)
                {
                    Xsl = "";
                }

                if (DataSources.Count == 0)
                {
                    var control = new CrossSiteDataSource();
                    control.ID = "DSC";
                    control.Query = "";

                    control.ContentType = "Item";
                    control.ListFields = "Title";
                    control.Lists = "100";
                    control.TopLevelSite = "/";
                    control.QueryRowLimit = "100";

                    DataSources.Add(control);
                    Xsl = Strings.XSL;
                    ParameterBindings = Strings.ParameterBindings;
                }
            }

            _assemblyReferences = new[] { DataSourcePrefix };

            try
            {
                base.CreateChildControls();
            }
            catch (Exception e)
            {
                base.Controls.Add(new LiteralControl(e.ToString()));
            }

            // Create the DataTable.
            tableOut = new DataTable();
        }

        protected override void PreprocessPostBackEvent(ref string eventArg)
        {
            if (FixSorting && !string.IsNullOrEmpty(eventArg))
            {
                int idx = eventArg.IndexOf("dvt_sortfield");
                if (idx >= 0)
                {
                    int end = eventArg.IndexOf("}", idx);
                    idx += 15;
                    string colName = eventArg.Substring(idx, end - idx);
                    string sortType = null;

                    idx = eventArg.IndexOf("dvt_sorttype");
                    if (idx >= 0)
                    {
                        end = eventArg.IndexOf("}", idx);
                        idx += 14;
                        sortType = eventArg.Substring(idx, end - idx);
                    }

                    if (sortType == null || sortType == "undefined" || sortType == "text")
                    {
                        Type type;
                        type = GetColType(colName);

                        if (type != null && type != typeof(string))
                        {
                            string newSortType;
                            newSortType = type == typeof(Decimal) ? "number" : "text";

                            if (sortType != null)
                            {
                                eventArg = eventArg.Replace("dvt_sorttype={" + sortType, "dvt_sorttype={" + newSortType);
                            }
                            else
                            {
                                eventArg += ";dvt_sorttype={" + newSortType + "}";
                            }
                        }
                    }
                }
            }
        }

        private Type GetColType(string colName)
        {
            ATabularDataSource source = TabularDataSource;
            ATabularDataSourceView view = source.GetView();
            DataTable dt = view.GetData();

            if (dt != null)
            {
                DataColumn col = dt.Columns[colName];
                if (col != null && col.ExtendedProperties.ContainsKey(FieldInfo.ExtPropKey))
                {
                    SPFieldType fieldType = col.ExtendedProperties[FieldInfo.ExtPropKey] is SPFieldType
                                                ? (SPFieldType)col.ExtendedProperties[FieldInfo.ExtPropKey]
                                                : SPFieldType.Invalid;

                    switch (fieldType)
                    {
                        case SPFieldType.Integer:
                        case SPFieldType.Number:
                        case SPFieldType.Currency:
                            return typeof(decimal);

                        case SPFieldType.DateTime:
                            return typeof(DateTime);
                    }
                }
            }

            return null;
        }

        protected override XPathNavigator GetXPathNavigator(string viewPath)
        {
            timeQueryStart = DateTime.Now;
            XPathNavigator navigator;
            //ATabularDataSource ds = this.TabularDataSource;
            //if (ds == null)
            //{

            navigator = base.GetXPathNavigator(viewPath);
            //}
            //else
            //{
            //    DataTable table = ds.GetView().GetData();
            //    DataSet set = new DataSet("NewDataSet");
            //    set.Tables.Add(table);
            //    string xml = "<dsQueryResponse>" + set.GetXml() + "</dsQueryResponse>";
            //    StringReader sr = new StringReader(xml);
            //    XPathDocument doc = new XPathDocument(sr);
            //    navigator = doc.CreateNavigator();
            //}
            timeQueryEnd = DateTime.Now;
            return navigator;
        }

        public override void DataBind()
        {
            timeRenderStart = DateTime.Now;
            base.DataBind();
            timeRenderEnd = DateTime.Now;
        }

        protected override void RenderWebPart(HtmlTextWriter output)
        {
            base.RenderWebPart(output);

            ATabularDataSource ds = TabularDataSource;

            string messages = ds != null ? ds.ProcessingMessages : null;

            if (Debug && ds != null)
            {
                TimeSpan querySpan = timeQueryEnd - timeQueryStart;
                TimeSpan renderSpan = timeRenderEnd - timeRenderStart - querySpan;

                output.Write("<font color=blue>Query time: " + querySpan.TotalMilliseconds + " / " +
                             timeQueryStart.ToString("mm:ss:fffffff") + " - " + timeQueryEnd.ToString("mm:ss:fffffff") +
                             "</font><br>");

                ExecutionStats stats = ds.GetStats();
                output.Write("<font color=blue>DS: " + stats.ElapsedQueryTime.TotalMilliseconds + " total"
                             +
                             (stats.ElapsedPostProcessingTime != TimeSpan.Zero
                                  ? ", " + stats.ElapsedPostProcessingTime.TotalMilliseconds + " post"
                                  : "") + "</font><br>");

                output.Write("<font color=blue>Transform time: " + renderSpan.TotalMilliseconds + " / " +
                             timeRenderStart.ToString("mm:ss:fffffff") + " - " + timeRenderEnd.ToString("mm:ss:fffffff") +
                             "</font><br><br>");

                if (messages != null)
                {
                    output.Write("<div style=\"color:red;margin-top:15px\">");
                    output.Write(messages.Replace("\n", "<br />"));
                    output.Write("</div>");
                }

                Object type = ds.GetView().GetData();

                bool isDataTable = type is DataTable;
                if (isDataTable || type is DataSet)
                {
                    string xml;
                    if (isDataTable)
                    {
                        var sw = new StringWriter();
                        var dt = (DataTable)type;
                        if (string.IsNullOrEmpty(dt.TableName))
                            dt.TableName = "Rows";
                        dt.WriteXml(sw);
                        xml = sw.GetStringBuilder().ToString();
                    }
                    else
                    {
                        xml = ((DataSet)type).GetXml();
                    }
                    output.Write("<font color=red>RESULTING XML</font><br><font color=blue>");
                    output.Write("<br><br><font color=red>New Rollup2</font><br><font color=blue>");
                    output.Write("<pre>" + SPEncode.HtmlEncode(xml) + "</pre>");
                    output.Write("</font><br>");
                }

                DataSourceCollection sources = ds.GetView().CachedDataSources;
                if (sources != null)
                {
                    foreach (Control dsrc in sources)
                    {
                        var sw = new StringWriter();
                        ATabularDataSourceView view = ((ATabularDataSource)dsrc).GetView();
                        view.ForceRollupRerun();
                        DataTable dt = view.GetData();
                        if (dt == null) continue;
                        if (string.IsNullOrEmpty(dt.TableName))
                            dt.TableName = "Rows";
                        dt.WriteXml(sw);
                        string xml2 = sw.GetStringBuilder().ToString();

                        output.Write("<font color=red>DEBUG XML for " + dsrc.ID + ":</font><br><font color=blue>");
                        output.Write("<pre>" + SPEncode.HtmlEncode(xml2) + "</pre>");
                        output.Write("</font><hr />");
                    }
                }
            }
        }

        protected override bool ConnectToDataSourceControl()
        {
            if (AllPropertiesSet())
                return base.ConnectToDataSourceControl();
            else
                return false;
        }

        public override bool ShouldProvideRows()
        {
            return true;
        }


        private bool AllPropertiesSet()
        {
            try
            {
                ATabularDataSource ds = TabularDataSource;
                if (ds is AggregateDataSource)
                {
                    var agw = (AggregateDataSource)ds;
                    return agw.Sources.Count > 0;
                }

                if (ds is RollUpDataSource)
                {
                    var ruds = (RollUpDataSource)ds;
                    return ruds.Lists != null && ruds.GetFields().Count > 0;
                }
                return ds != null;
            }
            catch
            {
                return false;
            }
        }

        protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
        {
            argList.AddExtensionObject("http://schemas.portalsolutions.net/Rollup/DataView/", new XsltExt());
            argList.AddExtensionObject(ExsltNamespaces.GDNDatesAndTimes, new ExsltDatesAndTimes());
            argList.AddExtensionObject(ExsltNamespaces.GDNMath, new ExsltMath());
            argList.AddExtensionObject(ExsltNamespaces.GDNRegularExpressions, new ExsltRegularExpressions());
            argList.AddExtensionObject(ExsltNamespaces.GDNSets, new ExsltSets());
            argList.AddExtensionObject(ExsltNamespaces.GDNStrings, new ExsltStrings());
        }

        #region Nested type: XsltExt

        private class XsltExt
        {
            private readonly Dictionary<int, string> values = new Dictionary<int, string>(8);

            public bool NameChanged(object str, int slot)
            {
                string prev = values.ContainsKey(slot) ? values[slot] : null;
                string val = str != null ? str.ToString() : null;

                values[slot] = val;
                return val != prev;
            }
        }

        #endregion

        #region ImplementProvider

        private bool _connected;
        private bool _registrationErrorOccurred;
        private DataTable tableOut;

        public RollUpDataFormWebPart()
        {
            FixSorting = true;
        }

        public event ListProviderInitEventHandler ListProviderInit;
        public event ListReadyEventHandler ListReady;
        public event PartialListReadyEventHandler PartialListReady;

        public override void EnsureInterfaces()
        {

            try
            {

                RegisterInterface("MyListProviderInterface", //InterfaceName    
                                  InterfaceTypes.IListProvider, //InterfaceType
                                  UnlimitedConnections, //MaxConnections
                                  ConnectionRunAt.Server, //RunAtOptions
                                  this, //InterfaceObject
                                  "", //InterfaceClientReference
                                  "Provide List To", //MenuLabel
                                  "Provides a list to a consumer Web Part."); //Description
            }
            catch (SecurityException se)
            {
                _registrationErrorOccurred = true;
            }
        }


        public override ConnectionRunAt CanRunAt()
        {
            return ConnectionRunAt.Server;
        }


        public override void PartCommunicationConnect(
            string interfaceName,
            WebPart connectedPart,
            string connectedInterfaceName,
            ConnectionRunAt runAt)
        {
            // Keep track of connection state.
            if (interfaceName == "MyListProviderInterface")
            {
                _connected = true;
            }
        }


        public override void PartCommunicationInit()
        {
            // Ensure that all of the Web Part's controls are created.
            EnsureChildControls();

            // Check if connected.
            if (_connected)
            {
                // If there is a listener, fire ListProviderInit event.
                if (ListProviderInit != null)
                {
                    Object type = TabularDataSource.GetView().GetData();

                    bool isDataTable = type is DataTable;
                    if (isDataTable)
                    {
                        tableOut = (DataTable)type;

                        var fieldList = new string[tableOut.Columns.Count];
                        int i = 0;
                        foreach (DataColumn dc in tableOut.Columns)
                        {
                            fieldList[i] = dc.ColumnName;
                            i++;
                        }
                        //Create the ListProviderInitEventArgs object for 
                        //the ListProviderInit event.
                        var listProviderInitArgs = new ListProviderInitEventArgs();

                        // Set the list field names.
                        listProviderInitArgs.FieldList = fieldList;
                        listProviderInitArgs.FieldDisplayList = fieldList;

                        // Fire the ListProviderInit event.
                        ListProviderInit(this, listProviderInitArgs);
                    }
                }
            }
        }

        // Step #8: Override the PartCommunicationMain method.
        // The PartCommunicationMain method is called by the Web Part 
        // infrastructure on the client during the ASP.NET PreRender
        // event to allow the part to pass its primary data to the 
        // other connected parts.
        // It is important to always fire the ListReady or 
        // PartialListReady event. Some parts
        // may not behave properly if they are left waiting for this 
        // information.
        public override void PartCommunicationMain()
        {
            // Ensure that all of the Web Part's controls are created.
            EnsureChildControls();

            //Check if connected
            if (_connected)
            {
                // If there is a listener, fire the ListReady event.
                if (ListReady != null)
                {
                    // Create the ListReadyEventArgs object for the 
                    // ListProviderInit event.
                    var listReadyArgs = new ListReadyEventArgs();

                    // If user clicked button, send the value.

                    listReadyArgs.List = tableOut;

                    // The user didn't click the button, so send a
                    // null DataTable to the consumer Web Part.


                    // Fire the ListReady event.
                    // The consumer Web Part will receive the 
                    // DataTable.
                    ListReady(this, listReadyArgs);
                }
            }
        }



        #endregion
    }
}